#ifndef CHEETAH_GRAPHICS_MODEL_GL_H
#define CHEETAH_GRAPHICS_MODEL_GL_H

#include "cheetah/core.h"
#include "cheetah/model/triangle_mesh.h"
#include "cheetah/geom.h"


namespace cheetah {
namespace graphics {
using namespace geom;
using cheetah::model::MeshSet;
using cheetah::model::TriangleMesh;
using cheetah::graphics::Material;
/* 
 * The implementation should make sure that sizeof(Normal) == sizeof(Position)
 */
class ModelGL {
public:
  enum Options {    
    kWithNormal = 1,
    kLighting = 2,
  };
  struct Hint {
    Hint(const int offset_buff_size, const int vert_buff_size, 
         const int idx_size) 
        : offset_buff_size(offset_buff_size), 
          vert_buff_size(vert_buff_size),
          idx_buff_size(idx_buff_size) { }
    Hint() : offset_buff_size(0), vert_buff_size(0), idx_buff_size(0) { }
    int offset_buff_size;
    int vert_buff_size;
    int idx_buff_size;
  };

  ModelGL(const Hint hint);
  ModelGL();
  ~ModelGL();
  // meshes should not be deleted after loading, in order to make it work.  
  int LoadModel(const TriangleMesh *mesh);
  void DrawInstanced(const int n_instances, int opts = 0) const;
  void Draw(int opts = 0) const;
  void DrawBound() const;

  void set_normal_slot(const int idx) { normal_slot_ = idx; }
  void set_vertex_slot(const int idx) { vertex_slot_ = idx; }
  int vertex_slot() const { return vertex_slot_; }
  int normal_slot() const { return normal_slot_; }  
private:
  void Initialize();
  void ReleaseVBOs();  
  int AllocateVertVBO(const int size);
  int AllocateIdxVBO(const int size);
  void BeginDraw() const;
  void EndDraw() const;

private:    
  uint32 vert_vbo_;
  uint32 idx_vbo_;  
  int vertex_slot_;
  int normal_slot_;
  int vert_vbo_size_;
  int idx_vbo_size_;  
  const Hint hint_;
  const TriangleMesh *mesh_;
};

typedef std::vector<ModelGL*> ModelSetGL;

class Quad3DGL {
public:  
  Quad3DGL(const float x1, const float y1, const float z1,
         const float x2, const float y2, const float z2,
         const float nx, const float ny, const float nz);
  Quad3DGL(const Point &p1, const Point &p2, const Normal &n);
private:
  float vertices_[12];
  float tex_coords_[8];
  static const float indices[6];
};

class Quad2DGL {
public:  
  Quad2DGL(const float x1, const float y1, const float x2, const float y2) {
    CreateQuad2D(x1, y1, x2, y2);
  }
  Quad2DGL(const Point &p1, const Point &p2) {
    CreateQuad2D(p1.x, p1.y, p2.x, p2.y);
  }
  void set_tex_coord(const float *tex_coord) {
    memcpy(tex_coords_, tex_coord, sizeof(tex_coords_));
  }
  void set_tex_coord(const int *tex_coord) {
    memcpy(tex_coords_, tex_coord, sizeof(tex_coords_));
  }
  void set_vertex_slot(const int slot) { vertex_slot_ = slot; }
  void set_tex_coord_slot(const int slot) { tex_coord_slot_ = slot; }
  void Draw();
private:
  void CreateQuad2D(const float x1, const float y1, 
                    const float x2, const float y2) {
    vertices_[0] = x1; vertices_[1] = y1; // left-bottom
    vertices_[2] = x2; vertices_[3] = y1; // right-bottom
    vertices_[4] = x2; vertices_[5] = y2; // right-top
    vertices_[6] = x1; vertices_[7] = y2; // left-top
    vertex_slot_ = 0; tex_coord_slot_ = 1;
    tex_coords_[0] = 0.f; tex_coords_[1] = 0.f; 
    tex_coords_[2] = 1.f; tex_coords_[3] = 0.f; 
    tex_coords_[4] = 1.f; tex_coords_[5] = 1.f; 
    tex_coords_[6] = 0.f; tex_coords_[7] = 1.f;
    CreateVBO();
  }
  void CreateVBO();
private:
  int vertex_slot_, tex_coord_slot_;
  float vertices_[8];
  union {
    float tex_coords_[8];
    int i_tex_coords_[8];
  };
  uint32 vert_vbo_;
  uint32 idx_vbo_;
  static const uint32 indices_[6];
};

class ScreenQuad : public Quad2DGL {
public:
  ScreenQuad();
  const float *mvp() const { return &mvp_[0][0]; }
private:
  Matrix4x4 mvp_;
};

} // graphics
} // cheetah

#endif // CHEETAH_GRAPHICS_MODEL_GL_H